package org.gnu.williamjoy.marycircles.model;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

import org.gnu.williamjoy.marycircles.R;
import org.gnu.williamjoy.marycircles.control.GameState;
import org.gnu.williamjoy.marycircles.view.CirclesDrawView;

import android.graphics.RectF;
import android.util.Log;

/**
 * 
 * @author Lichun Wei<lichun.william@gmail.com>
 * 
 */
public class CirclesRoom extends Thread {

    private boolean running = true;

    private GameState game;

    public boolean isRunning() {
        return running;
    }

    public void setRunning(boolean running) {
        this.running = running;
    }

    @Override
    public void run() {
        int sleepTime = view.getResources().getInteger(
                R.integer.update_interval);
        while (true) {
            if (running) {
                step();
                resolveCollision();
                this.view.postInvalidate();
            } else {
                Thread.yield();
            }
            try {
                Thread.sleep(sleepTime);
            } catch (InterruptedException e) {
                Log.d("UpdatingThread", e.toString());
            }
        }
    }

    private int escape_index = 2;

    private final ReentrantLock lock;

    private CirclesDrawView view;

    private RectF rectF;

    public CirclesRoom(CirclesDrawView view, GameState game) {
        super();
        this.lock = new ReentrantLock();
        this.view = view;
        this.rectF = view.getRectF();
        this.circles = new LinkedList<MovingCircle>();
        this.game = game;
    }

    public void add(MovingCircle c) {

        this.lock.lock();

        this.circles.add(c);
        if (this.circles.size() > game.getMaxCircles()) {
            this.circles.get(this.escape_index).setEscape(true);
            this.escape_index++;
        }
        this.circlesCache = new MovingCircle[this.circles.size()];
        this.circlesCache = circles.toArray(circlesCache);

        this.lock.unlock();

    }

    public void remove(MovingCircle c) {

        this.lock.lock();

        this.circles.remove(c);
        this.escape_index--;
        this.circlesCache = new MovingCircle[this.circles.size()];
        this.circlesCache = circles.toArray(circlesCache);
        this.lock.unlock();
    }

    public MovingCircle[] getCircles() {
        return circlesCache;
    }

    /**
     * process circles move one step
     */
    public void step() {
        float x;
        float y;
        for (MovingCircle m : circlesCache) {
            if (m == null) {
                continue;
            }
            x = m.x + m.dx;
            y = m.y + m.dy;
            if (m.isEscape()) {
                if ((x + m.radius < 0 || x - m.radius > rectF.right)
                        || (y + m.radius < 0 || y - m.radius > rectF.bottom)) {
                    this.remove(m);
                }
            } else {

                if (x > rectF.right - m.radius) {
                    m.dx = 0 - Math.abs(m.dx);

                } else if (x < m.radius) {
                    m.dx = Math.abs(m.dx);
                }
                if (y > rectF.bottom - m.radius) {
                    m.dy = 0 - Math.abs(m.dy);
                } else if (y < m.radius) {
                    m.dy = Math.abs(m.dy);
                }
            }
            m.x = x;
            m.y = y;
        }
    }

    /**
     * http://en.wikipedia.org/wiki/Elastic_collision#One-dimensional_Newtonian
     * TODO: make billiard collision currently just simplify circle as a point
     * with mass
     */
    public void resolveCollision() {
        MovingCircle[] snapshot = circlesCache;
        for (int i = 0; i < snapshot.length - 1; i++) {
            MovingCircle A = snapshot[i];
            if (A == null)
                continue;
            float totalMass;
            float t1;
            float A_XMomentum = A.getXMomentum();
            float A_YMomentum = A.getYMomentum();
            for (int j = i + 1; j < snapshot.length; j++) {
                MovingCircle B = snapshot[j];
                if (B == null)
                    continue;
                float l = A.distanceSquare(B);
                float limit = (A.radius + B.radius) * (A.radius + B.radius);
                t1 = A.mass - B.mass;
                totalMass = A.mass + B.mass;
                if (A.collisionDetect(B)) {

                    float dxA = (A.dx * (t1) + 2 * B.getXMomentum())
                            / (totalMass);
                    float dxB = (B.dx * (-t1) + 2 * A_XMomentum) / (totalMass);
                    float dyA = (A.dy * (t1) + 2 * B.getYMomentum())
                            / (totalMass);
                    float dyB = (B.dy * (-t1) + 2 * A_YMomentum) / (totalMass);
                    // What if you really move after collision
                    l = (A.x + dxA - B.x - dxB) * (A.x + dxA - B.x - dxB)
                            + (A.y + dyA - B.y - dyB) * (A.y + dyA - B.y - dyB);
                    // Can you solve collision this time
                    if (l >= limit) {
                        // yes, change velocity
                        A.dx = dxA;
                        A.dy = dyA;
                        B.dx = dxB;
                        B.dy = dyB;
                        A.hit();
                        B.hit();
                        game.playSound(R.raw.hit);
                        /*
                         * when mary and william meet, and room is full of
                         * circle Level Up
                         */
                        int max = game.getMaxCircles();
                        if (j == 1 && snapshot.length > max
                                && snapshot.length < max + 3) {
                            game.levelUp(A.getCollision(), B.getCollision());
                        }
                    }
                }
            }
        }
    }


    private List<MovingCircle> circles;
    private MovingCircle circlesCache[] = new MovingCircle[0];
}
